#import "Celestial.h"
#import "TiffManager.h"
#import "Thinker.h"
#import <appkit/NXBitmapImageRep.h>
#define PI (3.141592653589)
#define MAXMULTBODY 10				//total multiple objects allowed

@implementation Celestial

- init
{
    	[super init];
		tiffStorageIndex = 0;
		animationIndex = 0;

		tiffManagerObject = [[TiffManager alloc] init];
		tiffStorage = [tiffManagerObject returnTiffStorage];
		
		bodyList = [[List alloc] init];
			
		avoidStorage = [[Storage allocFromZone:[self zone]] initCount:0
    	 elementSize:sizeof(AvoidStruct) description:"{ffff}"];

		okToDoAnim = NO;
    	currentBodyIndex = 0;
		cycleStartIndex = 0;
		cycles = 0;
		currentCycle = 1;
		bodyCount = 0;
		tiffsNeedBuilding = YES;
		okToDoAnim = NO;
		multDelay = floor(randBetween(objectSpeed * 0.1,
		 (objectSpeed * 0.1) + 10));
		currentMultTotal = 1;
		[starsObject setStarsStopped];
		PSsetlinewidth(4.0);  //0.0 is more efficient but I liked this better
			
    return self;
    
}
// Every module should have a first state method
//since class info only gets loaded once - therefore only 1 init called

- setFirstState
{
    firstState = YES;
	okToDoAnim = NO;
	cycleStartIndex = currentBodyIndex;

	bodyCount = 0;
	tiffsNeedBuilding = YES;
	okToDoAnim = NO;
	multDelay = floor(randBetween(objectSpeed * 0.1,
		(objectSpeed * 0.1) + 10));
	
	
	
	currentMultTotal = 1;
	starsStopping = NO; //[starsObject isStopping];  //should be NO
	starsStopped = NO; //[starsObject isStopped]; //should be NO

	bodiesDone = 0;
	currentCycle = 1;
	
	return self;
}
- (BOOL)doUntilDone
{
BOOL done;
int ii,count;

done = NO;
	
if(!starsStopping){

	if(!okToDoAnim){ //go build the tiffs
		if(tiffsNeedBuilding){
			tiffsNeedBuilding = [tiffManagerObject createTiffs];
		}
		else { //do the animation set up
				totalBodies = [tiffStorage count];
				animBuilt = (MAXANIMATIONS > totalBodies) ? totalBodies :
				 MAXANIMATIONS;  //pick smaller
				[self changeBodyList:currentBodyIndex];
				if(soundEnabled)
					[pwrUpSnd play];
				[starsObject startStars]; 
				starsStopping = NO; //[starsObject isStopping];  //should be NO
				starsStopped = NO; //[starsObject isStopped]; //should be NO
				okToDoAnim = YES;
		}
	}  //now do the animation
	else {
		if(nextStartTime < 0){ //time to do animation
			count = [bodyList count];
			multDelay--;
			if(multDelay < 0){ // reset delay timer for next multiple object
				currentMultTotal++;
				if(currentMultTotal > count)
					currentMultTotal = count;
				multDelay = floor(randBetween(objectSpeed * 0.1,
				 (objectSpeed * 0.1) + 10));
				
			}
			if(count){ // there are some objects to do
				for(ii = 0;ii < currentMultTotal;ii++){
					currentBody = [bodyList objectAt:ii];
        			if([currentBody doUntilDone]){
						[bodyList removeObjectAt:ii];
						ii--;
						currentMultTotal--;
        				currentBody = [currentBody free];
					}
				}
			[starsObject setAvoidRect:avoidStorage];//set avoidance for all
													//bodies	
			}
			else {  // object has zipped off screen
					//in case of multiple - last one has zipped off screen
				[self setNextStartTime];
				currentMultTotal = 1;
				multDelay = floor(randBetween(objectSpeed * 0.1,
				 (objectSpeed * 0.1) + 10));
				bodyCount++;
				if(bodyCount >= animBuilt ){  //complete cycle is done
					currentCycle++;
					if(currentCycle > cycles){//done with this module
					
						if(!([starsObject isStopping])){
							if(soundEnabled)
								[pwrDownSnd play];
							[starsObject stopStars];
							starsStopping = YES;
						}
						currentBodyIndex++;
  						if(currentBodyIndex >= totalBodies)
	       					currentBodyIndex = 0;
						cycleStartIndex = currentBodyIndex;
					}
					else {
						currentBodyIndex = cycleStartIndex;
						bodyCount = 0;
						[self changeBodyList:currentBodyIndex]; //create body
					}
				}
				else {
					currentBodyIndex++;
					if(currentBodyIndex >= totalBodies)
	       				currentBodyIndex = 0;
					[self changeBodyList:currentBodyIndex]; //create body

				}
			}
		}
		else
			nextStartTime--;
	}
}
else{ //stars are stopping
	if(starsStopped){
			PSsetlinewidth(0.0);  //reset
		done = YES;
	}
}
return done;
}


- setStartInterval: (Slider *)sender;
{

	startInterval = [sender intValue];
	nextStartTime = 0;
	return self;

}

- setNextStartTime
{
    nextStartTime = startInterval;
    return self;
}
- setPwrDownSnd:(Sound *)theSound
{
	pwrDownSnd = theSound;
	return self;
}
- setPwrUpSnd:(Sound *)theSound;
{
	pwrUpSnd = theSound;
	return self;
}

- setObjectSpeed: (Slider *)sender
{

	objectSpeed =([sender maxValue] + [sender minValue]) - [sender floatValue];
	return self;
}

- setBoundsRect:(NXRect *)r
{

    bounds.origin.x = r->origin.x;
    bounds.origin.y  = r->origin.y;
    bounds.size.width = r->size.width;
    bounds.size.height = r->size.height;
    
    return self;
}


- changeBodyList:(int)index
{

int ii;
int total;
AvoidStruct avoidStruct;

	for(ii=0;ii < 4;ii++)  //init the lastTheta array
		lastTheta[ii] = -1;
		
	total = floor(randBetween(2,MAXMULTBODY));
	
	if([bodyList count]){
		[bodyList freeObjects];
	}

	[avoidStorage empty];
	isMult = ((ImageStruct *)[tiffStorage elementAt:
		index])->isMult;
		
	avoidStruct.avoid.origin.x = 0.0;
	avoidStruct.avoid.origin.y = 0.0;
	avoidStruct.avoid.size.width = 0.0;	
	avoidStruct.avoid.size.height = 0.0;

	if(isMult){
		for(ii=0;ii < total;ii++){
			currentBody = [self createBody:index];
			[currentBody setBodyIndex:ii];
			[bodyList addObject:currentBody];
			[avoidStorage addElement:&avoidStruct];
		}
	}
	else{
		currentBody = [self createBody:currentBodyIndex];
		[bodyList addObject:currentBody];
		[avoidStorage addElement:&avoidStruct];

	}
	
	return self;
}


- (Body *)createBody:(int)index
{
Body		*body;
float 		theta;	//angle of travel for new body
float 		multObjectSpeed;

    body = [[Body alloc] init];
	[body setAvoidStorage:avoidStorage];
 
    [body setImageList:((ImageStruct *)[tiffStorage
	 elementAt:index])->imageList];
	 
	[body setNumberOfFrames];
    [body setBoundsRect:(NXRect *)&bounds]; //must be done after setCellSize
	
	if(isMult){
		multObjectSpeed = objectSpeed + floor(randBetween(0,10));
		[body setObjectSpeed:multObjectSpeed];
		theta = [self genUniqueTheta];  //make sure mult objects stay clear
										//of each other

	}
	else{
		[body setObjectSpeed:objectSpeed];
		theta = randBetween(0,(2*PI));
	}
	[body setAngle:theta];
	[body setStarsOutlet: (id)starsObject];
	return(body);
}

- starsStopped
{
	starsStopped = YES;
	return self;
}
- setStarsOutlet:(id)starsOutlet;
{
	starsObject = starsOutlet;
	return self;
}// just a prototype - not used in this module
- setStarSpeed:sender
{
	return self; 	
}

//The idea here was that individual modules could decide
//how to interpret the slider value
// if you only want this module to do one pass etc.

- (int)setCycleValue:(int)value;
{
int mutiplier;
	// since this module is interesting make it 5 times the slider value
	// could be zero
	
	mutiplier = 5;
	if(!value)
		value = 1;
	else
		value *= mutiplier;
	cycles = value;
	currentCycle = 1;
	return mutiplier;
}

- windowSizeChanged
{
	if(currentBody){  //if one exists

		[self setNextStartTime];
		//[bodyList freeObjects];
		//printf("window size changed bodylist has %d elements\n",
		//[bodyList count]);
		
		currentMultTotal = 1;
		currentBody = nil;
		[self changeBodyList:currentBodyIndex];
	}

	return self;
}
- freeResources
{
	[tiffManagerObject freeTiffs];
	if ([bodyList count]){
		[bodyList freeObjects];
	}
	return self;
}
// just a prototype - sound isn't used in this module
- setSoundEnabled:(BOOL)enabled
{
	soundEnabled = enabled;
	return self;
}

//for each 4 in a row - make sure random theta is
// 40 degrees from any of the others	
- (float)genUniqueTheta
{
int ii;
int maxTest = 4;
float theta;
BOOL thetaOK = NO;

	theta = randBetween(0,(2*PI));

	while(!thetaOK){
		thetaOK = YES;
		for(ii = 0;ii < maxTest;ii++){
			if(lastTheta[ii] < 0)
				continue;
				//.69 = 40 degrees
			if(theta > (lastTheta[ii] - .69) && theta < (lastTheta[ii] + .69)){
				thetaOK = NO;
				theta = randBetween(0,(2*PI));
			}
		}  
	}
	for(ii = 1;ii < maxTest;ii++){
		lastTheta[ii-1] = lastTheta[ii];
	}
	lastTheta[ii-1] = theta;

	
	
	return theta;
}
@end
